package marble.model {

	import flash.display.Sprite;
	import flash.external.ExternalInterface;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.Dictionary;
	
	import marble.Facade;
	
	import mx.formatters.DateFormatter;

	public class Plan extends Sprite {

		private static const CELL_WIDTH:int = 120;
		private static const CELL_HEIGHT:int = 29;

		private static const ALTER_COLORS_1:Array = [0xeeeedd, 0xdedecd];
		private static const ALTER_COLORS_2:Array = [0xececec, 0xd8d8d8];
		private static const BLOCK_COLORS:Array = [0xffffe1, 0xffffff];
		private static const BORDER_COLOR:uint = 0x64829c;
		private static const HEADER_COLOR:uint = 0x9ca498;

		private static const OFFSET_X:int = 50;

		private static const TEXT_HEADER:TextFormat = new TextFormat("Tahoma", 13, 0xffffff, true, null, null, null, null,"center");
		private static const TEXT_SMALL_L:TextFormat = new TextFormat("Tahoma", 10, 0x888888, false, null, null, null, null,"left");
		private static const TEXT_SMALL_R:TextFormat = new TextFormat("Tahoma", 10, 0x888888, false, null, null, null, null,"right");
		private static const TEXT_LARGE:TextFormat = new TextFormat("Verdana", 12, 0x000000, false, null, null, null, null,"center");

		// some collisions may be transitive
		private var cumulativeCollisions:Dictionary;

		private static var facade:Facade = Facade.getInstance();

		private static const DAY_NAMES:Array = [
			facade.locale.translate("Monday"),
			facade.locale.translate("Tuesday"),
			facade.locale.translate("Wednesday"),
			facade.locale.translate("Thursday"),
			facade.locale.translate("Friday"),
			facade.locale.translate("Saturday"),
			facade.locale.translate("Sunday")
		];

		private var week:Array;
		private var monday:Date;
		
		private var minTime:int = 480;
		private var maxTime:int = 1320;

		private var addRem:Boolean = false;
		private var reg:Boolean = false;

		private var _userElements:Array;
		private var _showAll:Boolean = true;

		public function Plan(elements:Array,
							 monday:Date,
							 min:int,
							 max:int,
							 addRem:Boolean,
							 reg:Boolean,
							 userElements:Array) {
			super();

			this.addRem = addRem;
			this.reg = reg;
			this.monday = monday;
			this.userElements = userElements;

			minTime = min;
			maxTime = max;

			// add elements to week
			week = [[],[],[],[],[],[],[]];
			for each (var el:Element in elements) {
				(week[el.getDayOfWeek()] as Array).push(el);
			}

			// sort elements
			for each (var day:Array in week) {
				day.sortOn("timeMinutes", Array.NUMERIC);
			}

			redraw();
			adjustWrapperHeight();
		}

		public function get userElements():Array {
			return _userElements;
		}

		public function set userElements(value:Array):void {
			_userElements = value;
		}

		public function set showAll(value:Boolean):void {
			_showAll = value;
		}
		
		public function get showAll():Boolean {
			return _showAll;
		}

		private function adjustWrapperHeight():void {
			if (ExternalInterface.available) {
				ExternalInterface.call("setWrapperHeight", height + 50);
			}
		}

		public function redraw():void {

			// recalculate
			initCollisions();

			var count:int = numChildren;
			for ( var i:int = 0; i < count; i++) {
				removeChildAt(0);
			}

			graphics.clear();
			drawDays();
			drawTime();
			drawGrid();
			drawElements();
		}

		private function getDateString(i:int):String {

			var date:Date = new Date(monday);
			date.date += i;

			var formatter:DateFormatter = new DateFormatter();
			formatter.formatString = "YYYY-MM-DD";
			return formatter.format(date);
		}

		private function drawDays():void {
			graphics.beginFill(HEADER_COLOR);
			for (var i:int = 0; i < DAY_NAMES.length; i++) {

				// background
				var posX:int = OFFSET_X + i*(CELL_WIDTH + ((i > 0) ? 1 : 0));
				graphics.drawRect(posX, 0, CELL_WIDTH, CELL_HEIGHT*1.6);

				// actual text
				var textField:TextField = new TextField();
				textField.text = DAY_NAMES[i] + "\n" + getDateString(i);
				textField.width = CELL_WIDTH;
				textField.height = CELL_HEIGHT*1.6;
				textField.x = posX;
				textField.y = 5;
				textField.setTextFormat(TEXT_HEADER);
				addChild(textField);
			}
			graphics.endFill();
		}

		private function drawTime():void {

			var i_max:int = Math.ceil(maxTime - minTime) / 60;
			var firstHour:int = (minTime < 0) ?
				int((1440 + minTime) / 60) : int(minTime / 60);

			var height:int = 2*CELL_HEIGHT;
			for (var i:int = 0; i < i_max; i++) {

				// time bar background
				var posY:int = CELL_HEIGHT*1.6 + i*(height + ((i > 0) ? 1 : 0)); 
				graphics.beginFill(ALTER_COLORS_1[Math.floor(i/2)%2]);
				graphics.drawRect(0, posY, OFFSET_X - 1, height);
				graphics.endFill();

				// schedule background
				for (var j:int = 0; j < DAY_NAMES.length; j++) {
					var posX:int = OFFSET_X + j*(CELL_WIDTH + ((j > 0) ? 1 : 0));
					graphics.beginFill(ALTER_COLORS_2[Math.floor(i/2)%2]);
					graphics.drawRect(posX, posY, CELL_WIDTH, CELL_HEIGHT);
					graphics.drawRect(posX, posY + CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT);
					graphics.endFill();
				}

				// actual time
				var textField:TextField = new TextField();
				textField.text = ((i + firstHour) % 24) + ":00";
				textField.width = OFFSET_X - 1;
				textField.height = height;
				textField.x = 0;
				textField.y = posY;
				textField.setTextFormat(TEXT_SMALL_R);
				addChild(textField);

				// mark days' boundaries
				if (((i + firstHour) % 24) == 0) {
					graphics.lineStyle(2, 0x000000, 0.25);
					graphics.moveTo(0, posY);
					graphics.lineTo(posX + CELL_WIDTH, posY);
					graphics.lineStyle(1, 0x000000, 0);
				}
			}
		}

		private function drawGrid():void {
			var i_max:int = Math.ceil(maxTime - minTime) / 60;
			var lineOffset:int = ((2*CELL_HEIGHT) + 2) / 4 - 1;
			var height:int = 2*CELL_HEIGHT;
			for (var i:int = 0; i < i_max; i++) {
				var posY:int = CELL_HEIGHT*1.6 + i*(height + ((i > 0) ? 1 : 0));
				for (var j:int = 0; j < DAY_NAMES.length; j++) {
					var posX:int = OFFSET_X + j*(CELL_WIDTH + ((j > 0) ? 1 : 0));
					graphics.lineStyle(1, ALTER_COLORS_1[Math.floor((i+2)/2)%2]);
					graphics.moveTo(posX, posY + lineOffset);
					graphics.lineTo(posX + CELL_WIDTH, posY + lineOffset);
					graphics.moveTo(posX, posY + 2*lineOffset);
					graphics.lineTo(posX + CELL_WIDTH, posY + 2*lineOffset);
					graphics.moveTo(posX, posY + 3*lineOffset);
					graphics.lineTo(posX + CELL_WIDTH, posY + 3*lineOffset);
				}
			}
		}

		private function drawElements():void {
			for (var i:int = 0; i < week.length; i++) {

				var posX:int = OFFSET_X + i*(CELL_WIDTH + ((i > 0) ? 1 : 0));

				var lastElem:Element = null;
				var collisionsOffset:int = 0;
				for each (var elem:Element in week[i]) {
					
					// show only user's elements
					if (!showAll) {
						var present:Boolean = false;
						for (var k:int = 0; k < userElements.length; k++) {
							if (userElements[k].id == elem.id) {
								present = true;
								break;
							}
						}
						if (!present) continue;
					}

					var block_height:int = (4*CELL_HEIGHT / 120) * elem.duration;

					var collisionsFactor:int = numCollisionsFor(elem) + 1;
					if (lastElem == null || !collide(lastElem, elem)) {
						collisionsOffset = 0;
					} else {
						collisionsOffset++;
					}
					lastElem = elem;

					var block_width:int = CELL_WIDTH / collisionsFactor;
					var x:int = posX + (collisionsOffset * (block_width + 1));

					var offset_y:int = (elem.timeMinutes - minTime) * 59/60;
					var posY:int = CELL_HEIGHT*1.6 + offset_y;

					// background and border
					graphics.lineStyle(1, elem.is_session ? 0xff3333 : BORDER_COLOR, elem.is_session ? 0.5 : 0.75);
					if (reg) {
						// participant plan
						graphics.beginFill(isOnUserElements(elem) ? BLOCK_COLORS[0] : BLOCK_COLORS[1]);
					} else {
						// other plans
						graphics.beginFill(elem.is_plan ? BLOCK_COLORS[0] : BLOCK_COLORS[1]);
					}
					graphics.drawRect(x, posY + 1, block_width - 1, block_height - 1);
					graphics.endFill();

					// element time
					var textField:TextField = new TextField();
					textField.text = formatTime(elem.timeMinutes);
					textField.width = block_width;
					textField.x = x + 2;
					textField.y = posY;
					textField.setTextFormat(TEXT_SMALL_L);
					addChild(textField);

					var oldHeight:int = textField.textHeight;
					var margin_x:int = 6 / collisionsFactor;

					// element info
					textField = new TextField();
					textField.wordWrap = true;
					textField.text = elem.name;
					textField.width = block_width - 2*margin_x;
					textField.height = block_height - oldHeight;
					textField.x = x + margin_x;
					textField.setTextFormat(TEXT_LARGE);
					textField.y = posY + Math.max(oldHeight, (textField.height - textField.textHeight) / 2);
					addChild(textField);

					// basic button for the sake of detail viewing
					var btn:PlanInfoButton = new PlanInfoButton(elem);
					btn.x = x + block_width - btn.width - 2;
					btn.y = posY + 3;
					addChild(btn);

					// additional button for the organizers
					var btnAddRem:PlanAddRemButton = null;
					if (addRem) {
						btnAddRem = new PlanAddRemButton(elem);
						btnAddRem.x = btn.x - btnAddRem.width - 2;
						btnAddRem.y = posY + 3;
						addChild(btnAddRem);
					}
					
					// additional button for participants
					if (reg && !addRem) {
						var btnReg:PlanRegButton = new PlanRegButton(elem, userElements);
						btnReg.x = btn.x - btnReg.width - 2;
						btnReg.y = posY + 3;
						addChild(btnReg);				
					}
				}
			}
		}

		private function isOnUserElements(elem:Element):Boolean {
			var tmp:Boolean = false;
			if (userElements) {
				for (var j:int = 0; j < userElements.length; j++) {
					// must compare ids since there are two
					// separate lists with distinct objects
					if (userElements[j].id == elem.id) {
						tmp = true;
						break;
					}
				}
			}
			return tmp;
		}

		private function formatTime(min:int):String {
			var h:int = Math.floor(min / 60);
			var m:int = min - h * 60;
			if (m < 10) {
				return h + ":0" + m;
			} else {
				return h + ":" + m;
			}
		}

		private function numCollisionsFor(elem:Element):int {
			return cumulativeCollisions[elem];
		}

		private function initCollisions():void {
			cumulativeCollisions = new Dictionary();
			for (var i:int = 0; i < week.length; i++) {
				for each (var elem:Element in week[i]) {
					if (showAll || isOnUserElements(elem))
					cumulativeCollisions[elem] = nonTransitiveCollisions(elem)
					+ transitiveCollisions(elem, []);
				}
			}
		}

		private function nonTransitiveCollisions(elem:Element):int {
			var collisions:int = 0;
			for each (var e:Element in week[elem.getDayOfWeek()]) {
				if (showAll || isOnUserElements(e)) {
					if (e != elem && collide(e, elem)) {
						collisions++;
					}
				}
			}
			return collisions;
		}

		private function transitiveCollisions(elem:Element, exclude:Array):int {
			var collisions:int = 0;
			var day:Array = week[elem.getDayOfWeek()];
			for each (var e:Element in day) {
				if (e != elem && collide(e, elem)) {

					exclude.push(e);
					exclude.push(elem);

					for each (var tmp:Element in day) {
						if (exclude.indexOf(tmp) == -1 && !collide(tmp, elem) && collide(tmp, e)) {
							collisions++;
							collisions += transitiveCollisions(tmp, exclude);
						}
					}
				}
			}
			return collisions;
		}

		private function collide(e1:Element, e2:Element):Boolean {
			if (e1 != e2) {
				if (e1.timeMinutes == e2.timeMinutes) {
					return true;
				} else if (e1.timeMinutes < e2.timeMinutes) {
					if (e1.timeMinutes + e1.duration > e2.timeMinutes) {
						return true;
					}
					return false;
				} else if (e1.timeMinutes > e2.timeMinutes) {
					if (e1.timeMinutes < e2.timeMinutes + e2.duration) {
						return true;
					}
					return false;
				}
			}
			return false;
		}
	}
}